Asynchronous Behavior

  • Using this HTML as an example :

<div id="widget1">Widget 1</div>
<div id="widget2">Widget 2</div>
<div id="widget3">Widget 3</div>
<div id="status"></div>

Java Script

const widgets = [
  { id: "widget1", url: "/data1" },
  { id: "widget2", url: "/data2" },
  { id: "widget3", url: "/data3" },
];

const status = document.getElementById("status");

let completed = 0;

widgets.forEach(w => {
  const el = document.getElementById(w.id);
  el.innerHTML = "Loading...";

  fetch(w.url)
    .then(res => {
      if (!res.ok) throw new Error("Network error");
      return res.text();
    })
    .then(html => {
      el.innerHTML = html;
    })
    .catch(err => {
      el.innerHTML = "Error loading widget";
      console.error(err);
    })
    .finally(() => {
      completed++;
      if (completed === widgets.length) {
        status.innerHTML = "Dashboard loaded";
      }
    });
});
  • fetch()

    • Is asynchronous , but the JS runtime is single-threaded .

    • The browser’s networking stack handles the request in the background using OS threads or the network stack.

    • JS does not block  â€” it registers a callback (the .then ) to be executed later , when the network response arrives.

    • You don’t control threads directly; the browser runtime  schedules your callbacks on the event loop.

  • .then()

    • Attaches a callback function (a lambda) to the Promise returned by fetch() .

    • When the Promise resolves (the HTTP request finishes), the JS engine calls this lambda with the response ( res ).

  • Chaining .then()

    • return res.text()  returns another  Promise (it's asynchronous). The next .then()  in the chain automatically waits for that Promise to resolve.

    • In other words: each .then()  schedules the next step.

    • The next lambda runs after the previous Promise ( res.text() ) resolves.

    • Whatever this lambda returns (or doesn’t) is automatically wrapped in a new Promise.

    • JS chains Promises internally, so the next .catch()  or .finally()  knows when this step completes.

    • The lambda inside the first .then   can be synchronous or asynchronous .

    • If you do just console.log(res) , it’s synchronous.

    • If you return a Promise ( res.text() ), the next .then  waits for that Promise to resolve.

    • Rule of thumb:

      • If you return a Promise inside a .then , the next .then  is delayed until that Promise resolves .

      • If you return a plain value, it passes immediately to the next .then .

    • Chaining is only needed when the previous step produces a Promise (i.e., an async result).

  • .catch()

    • If any previous Promise in the chain rejects  (network failure, or you throw  inside a .then() ), this lambda is executed.

    • Equivalent in C to checking if (!ok)  and jumping to the error handler for that thread.

    • JS handles the propagation of errors through the chain automatically.

  • .finally()

    • Always runs after the chain settles, success or failure .

    • Equivalent to your “finally” block in C after the thread finishes, updating counters and printing “Dashboard loaded.”

Soft-Equivalence in C, using many threads

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

// Simulated HTTP request
int fetch(const char* url, char** response) {
    // pretend network request
    if (strcmp(url, "/fail") == 0) return 0; // simulate error
    *response = strdup("HTML content");
    return 1; // success
}

// Shared state
int completed = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
const int total_widgets = 3;

// Thread function to simulate async fetch
void* fetch_widget(void* arg) {
    const char* url = (const char*)arg;
    char* html = NULL;

    // Step 1 & 2: fetch and check response
    int ok = fetch(url, &html);

    if (ok) {
        // Step 3: update DOM (simulated)
        printf("%s: %s\n", url, html);
    } else {
        // Step 4: catch
        printf("%s: Error loading widget\n", url);
    }

    // Step 5: finally
    pthread_mutex_lock(&lock);
    completed++;
    if (completed == total_widgets) {
        printf("Dashboard loaded\n");
    }
    pthread_mutex_unlock(&lock);

    free(html);
    return NULL;
}

int main() {
    const char* widgets[3] = {"/data1", "/data2", "/data3"};
    pthread_t threads[3];

    // Launch all requests in parallel
    for (int i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, fetch_widget, (void*)widgets[i]);
    }

    // Wait for all threads to finish
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}
  • C threads were just a visual analogy for concurrency, not a literal one-to-one comparison.

  • In C, network requests are blocking by default. This C example used threads to simulate concurrent network requests.

  • A more faithful comparison would be C using non-blocking async I/O (e.g., libuv , epoll ) instead of threads, but that’s more complex and less intuitive.

Equivalence in C, using concurrency with libuv

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

int completed = 0;
const int total_widgets = 3;

// Simulated async HTTP request
void async_fetch(uv_loop_t* loop, const char* url, void (*callback)(const char*, int)) {
    // Use a timer to simulate network latency
    uv_timer_t* timer = malloc(sizeof(uv_timer_t));
    uv_timer_init(loop, timer);
    timer->data = (void*)url;

    uv_timer_start(timer, 
        (uv_timer_cb) (void (*)(uv_timer_t*)) [](uv_timer_t* t) {
            const char* url = (const char*)t->data;
            int success = 1; // simulate success/failure
            if (strcmp(url, "/fail") == 0) success = 0;
            callback(url, success);
            uv_timer_stop(t);
            free(t);
        },
        rand() % 1000, // simulate network delay
        0
    );
}

// Callback after each request
void on_fetch_complete(const char* url, int success) {
    if (success) {
        printf("%s: HTML content\n", url);
    } else {
        printf("%s: Error loading widget\n", url);
    }

    completed++;
    if (completed == total_widgets) {
        printf("Dashboard loaded\n");
    }
}

int main() {
    uv_loop_t* loop = uv_default_loop();

    const char* widgets[3] = {"/data1", "/data2", "/data3"};

    for (int i = 0; i < 3; i++) {
        async_fetch(loop, widgets[i], on_fetch_complete);
    }

    uv_run(loop, UV_RUN_DEFAULT);
    return 0;
}
  • async_fetch()

    • Simulates an HTTP request using a non-blocking timer.

  • on_fetch_complete()

    • Acts as the callback, similar to a .then()  in JS.

  • Requests are all started immediately and run concurrently in the event loop, without spawning separate OS threads.

  • When each request “finishes,” its callback executes on the main thread, just like JS Promises execute on the event loop.

  • We track completed  to know when all requests are done, analogous to .finally()  in JS.